package com.ds.lens.data.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.client.vo.LensApplicationNameOutVO;
import com.ds.lens.data.common.constant.AggregatorEnum;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.constant.DetectionTypeEnum;
import com.ds.lens.data.common.constant.MonitorStateEnum;
import com.ds.lens.data.common.util.HttpUtil;
import com.ds.lens.data.common.util.LensMQLBuilder;
import com.ds.lens.data.common.util.MapUtils;
import com.ds.lens.data.common.util.PastTimeRange;
import com.ds.lens.data.security.SecuritytConstants;
import com.ds.lens.data.service.dao.*;
import com.ds.lens.data.service.dao.entity.*;
import com.ds.lens.data.service.esservice.EventOperateService;
import com.ds.lens.data.service.metric.MetricQueryFactory;
import com.ds.lens.data.service.metric.MetricQueryService;
import com.ds.lens.data.vo.in.PagedConditionInVO;
import com.ds.lens.data.vo.in.Sort;
import com.ds.lens.data.vo.in.User;
import com.ds.lens.data.vo.in.v2.downtime.LensScheduleDowntimeInVO;
import com.ds.lens.data.vo.in.v2.monitor.*;
import com.ds.lens.data.vo.out.PageResult;
import com.ds.lens.data.vo.out.v2.integration.LensIntegrationDetailOutVO;
import com.ds.lens.data.vo.out.v2.integration.LensIntegrationOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorDefineOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorDetailsOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorNotifyOutVO;
import com.ds.lens.data.vo.out.v2.monitor.LensMonitorOutVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.ds.lens.data.security.SecuritytConstants.SSO_TIMEOUT;

/**
 * @author WeiShaoying
 * @date 2019/11/6 下午2:37
 */
@Slf4j
@Service
public class LensMonitorService extends BaseService {

    private static final String CTI_URL = "/api/v1/cti/bind/resolve-group";

    @Value("${lens.onduty.host}")
    private String onDutyHost;

    @Value("${lens.dynamic.algorithm.warm-up.period:7}")
    private int algorithmWarmUpPeriod;

    @Autowired
    LensMonitorSnapshotDao lensMonitorSnapshotDao;

    @Autowired
    LensMonitorDefineDao lensMonitorDefineDao;

    @Autowired
    LensMonitorDefineSnapshotDao lensMonitorDefineSnapshotDao;

    @Autowired
    LensMonitorTagsDao lensMonitorTagsDao;

    @Autowired
    LensApplicationService lensApplicationService;

    @Autowired
    LensIntegrationService lensIntegrationService;

    @Autowired
    EventOperateService eventOperateService;

    @Autowired
    LensMonitorStateService lensMonitorStateService;

    @Autowired
    LensScheduleDowntimeService lensScheduleDowntimeService;

    @Autowired
    MetricQueryFactory metricQueryFactory;

    @Autowired
    LensMonitorNotifySnapshotDao lensMonitorNotifySnapshotDao;

    @Autowired
    private LensExternalEventDao externalEventDao;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public void wakeUpMonitorByMonitorId(Long monitorId) {
        Asserts.assertNotNull(monitorId, Constants.ErrorCode.PARAM_ERROR, "monitorId can not be null.");
        //更新monitor状态
        LensMonitor lensMonitor = lensMonitorDao.selectByPrimaryKey(monitorId);
        if (null == lensMonitor || lensMonitor.getIsDelete()) {
            return;
        }
        LensMonitor update = new LensMonitor();
        update.setId(lensMonitor.getId());
        update.setRunStatus(Constants.MonitorRunStatusEnum.NORMAL.getCode());
        update.setModifiedTime(new Date());
        Asserts.assertTrue(lensMonitorDao.updateByPrimaryKeySelective(update) > 0, Constants.ErrorCode.SYSTEM_EXCEPTION);
        //闭合事件
        List<LensExternalEvent> lensExternalEvents = externalEventDao.selectIncompleteEvents(monitorId);
        if (!CollectionUtils.isEmpty(lensExternalEvents)) {
            for (LensExternalEvent externalEvent : lensExternalEvents) {
                LensExternalEvent updateEvent = new LensExternalEvent();
                updateEvent.setId(externalEvent.getId());
                updateEvent.setStatus(Constants.ExternalEventStatusEnum.AUTO_COMPLETION.getCode());
                updateEvent.setEndTime(new Date());
                Asserts.assertTrue(externalEventDao.updateByPrimaryKeySelective(updateEvent) > 0, Constants.ErrorCode.SYSTEM_EXCEPTION);
            }
        }
    }

    public JSONArray findCITBySessionId(String sessionId) {
        String url = onDutyHost + CTI_URL;
        JSONObject headers = new JSONObject();
        headers.put("email", "");
        headers.put("session", sessionId);
        Header header = new BasicHeader(SecuritytConstants.AUTHORIZATION, headers.toJSONString());
        ResponseEntity entity = HttpUtil.get(url, null, header, SSO_TIMEOUT);
        log.debug("findCITBySessionId ResponseEntity==" + JSONObject.toJSONString(entity));
        if (null == entity || entity.getStatusCode() != HttpStatus.OK) {
            return null;
        }
        JSONObject jsonObject = (JSONObject.parseObject(JSONObject.toJSONString(entity.getBody())));

        return jsonObject.getJSONArray("data");
    }

    public List<String> getGroupByFields(long integrationId, Long applicationId, String metricName) {
        LensIntegrationDetailOutVO integrationDetailOutVO = lensIntegrationService.findLensIntegrationsById(integrationId);
        Asserts.assertNotNull(integrationDetailOutVO, Constants.ErrorCode.DATA_NOT_EXIST);

        //Get MetricQueryService instance
        MetricQueryService metricQueryService = metricQueryFactory.getMetricQueryService(Constants.METRIC_SERVICE_PREFIX + integrationDetailOutVO.getName());
        //Call the getGroupByFields method
        return metricQueryService.getGroupByFields(integrationDetailOutVO, applicationId, metricName);
    }

    public PageResult<LensMonitorOutVO> listMonitor(PagedConditionInVO<LensMonitorListInVO> conditionInVO, User user) {
        getPageCondition(conditionInVO);
        if (CollectionUtils.isEmpty(conditionInVO.getCondition().getApplicationIds())) {
            checkUserApplication(user);
            conditionInVO.getCondition().setApplicationIds(user.getApplicationIds());
        } else {
            checkUserApplication(user, conditionInVO.getCondition().getApplicationIds());
        }
        if (conditionInVO.getSort() == null) {
            Sort sort = new Sort();
            sort.setField("modified_time");
            sort.setOrder("desc");
            conditionInVO.setSort(sort);
        }
        conditionInVO.getCondition().setSort(conditionInVO.getSort());
        Page<LensMonitor> page = lensMonitorDao.listMonitorByAppIdOrTags(conditionInVO.getCondition());
        if (CollectionUtils.isEmpty(page.getResult())) {
            return PageResult.of(Collections.emptyList(), 0L, page.getPageNum(), page.getPageSize());
        }
        Map<Long, String> integrationMap = getIntegrationMap();
        Map<Long, String> applicationMap = getApplicationMap();
        List<LensMonitorOutVO> lensMonitorOutVOList = new ArrayList<>(page.getPageSize());
        Long currentTime = System.currentTimeMillis();
        page.getResult().forEach(lensMonitor -> {
            LensMonitorOutVO outVO = new LensMonitorOutVO();
            BeanUtils.copyProperties(lensMonitor, outVO);
            if (!StringUtils.isEmpty(lensMonitor.getTags())) {
                outVO.setTags(lensMonitor.getTags().split(","));
            }
            if (lensMonitor.getMuteEndTime() != null) {
                long muteEndTime = (lensMonitor.getMuteEndTime().getTime() - currentTime) / 60000;
                outVO.setMuteEndTime((muteEndTime < 1 ? "少于1" : muteEndTime) + "分钟");
            }
            outVO.setIntegrationName(integrationMap.get(outVO.getIntegrationId()));
            outVO.setApplicationName(applicationMap.get(outVO.getApplicationId()));
            outVO.setSuspended(lensMonitor.getRunStatus() == Constants.MonitorRunStatusEnum.SUSPENDED.getCode());
            lensMonitorOutVOList.add(outVO);
        });
        return PageResult.of(lensMonitorOutVOList, page.getTotal(), page.getPageNum(), page.getPageSize());
    }

    private Map<Long, String> getIntegrationMap() {
        List<LensIntegrationOutVO> lensIntegrationOutVOS = lensIntegrationService.findLensIntegrationMenu();
        Map<Long, String> integrationMap = new HashMap<>(lensIntegrationOutVOS.size());
        if (!CollectionUtils.isEmpty(lensIntegrationOutVOS)) {
            lensIntegrationOutVOS.forEach(lensIntegrationOutVO -> {
                integrationMap.put(lensIntegrationOutVO.getId(), lensIntegrationOutVO.getName());
            });
        }
        return integrationMap;
    }

    private Map<Long, String> getApplicationMap() {
        List<LensApplicationNameOutVO> applicationList = lensApplicationService.listAllApplication();
        Map<Long, String> applicationMap = new HashMap<>(applicationList.size());
        if (!CollectionUtils.isEmpty(applicationList)) {
            applicationList.forEach(lensApplication -> {
                applicationMap.put(lensApplication.getId(), lensApplication.getName());
            });
        }
        return applicationMap;
    }


    public LensMonitorDetailsOutVO getMonitorDetails(Long id, User user) {
        LensMonitorDetailsOutVO lensMonitorDetailsOutVO = getMonitorDetails(id);
        if (lensMonitorDetailsOutVO != null) {
            checkUserApplication(user, lensMonitorDetailsOutVO.getApplicationId());
            LensMonitorDefineOutVO define = lensMonitorDetailsOutVO.getDefine();
            lensMonitorDetailsOutVO.setExpression(LensMQLBuilder.buildQueryDSL(
                    define.getMetricName(), AggregatorEnum.valueOf(define.getMetricAggr()).toString(),
                    lensMonitorDetailsOutVO.getIntegrationId().toString(),
                    MapUtils.merge(new HashMap<>(), define.getScope(), define.getMetricFilter()),
                    null, define.getMetricBy(), new PastTimeRange(Long.valueOf(lensMonitorDetailsOutVO.getDefine().getDuring())),
                    Long.valueOf(define.getWindowTime()), null));
        }
        return lensMonitorDetailsOutVO;
    }

    public List<LensMonitorDetailsOutVO> getMonitorDetailsByTags(Long applicationId, List<String> tags) {
        if (null == applicationId || CollectionUtils.isEmpty(tags)) {
            return Collections.emptyList();
        }
        List<LensMonitorTags> tagsList = lensMonitorTagsDao.queryByTags(tags, applicationId);
        if (CollectionUtils.isEmpty(tagsList)) {
            return Collections.emptyList();
        }
        return tagsList.stream().map(lensMonitorTags -> getMonitorDetails(lensMonitorTags.getMonitorId())).filter(Objects::nonNull).collect(Collectors.toList());
    }

    public LensMonitorDetailsOutVO getMonitorDetails(Long id) {
        LensMonitor monitor = lensMonitorDao.selectByPrimaryKey(id);
        if (monitor == null) {
            return null;
        }
        LensMonitorDefine define = lensMonitorDefineDao.selectByMonitorId(id);
        LensMonitorNotify notify = lensMonitorNotifyDao.selectByMonitorId(id);

        LensMonitorDetailsOutVO outVO = new LensMonitorDetailsOutVO();
        LensMonitorDefineOutVO defineOutVO = new LensMonitorDefineOutVO();
        LensMonitorNotifyOutVO notifyOutVO = new LensMonitorNotifyOutVO();

        BeanUtils.copyProperties(monitor, outVO);
        BeanUtils.copyProperties(define, defineOutVO);
        BeanUtils.copyProperties(notify, notifyOutVO);
        if (!StringUtils.isEmpty(define.getMetricBy())) {
            defineOutVO.setMetricBy(Arrays.asList(define.getMetricBy().split(",")));
        }
        outVO.setDefine(defineOutVO);
        outVO.setNotify(notifyOutVO);
        if (!StringUtils.isEmpty(monitor.getTags())) {
            outVO.setTags(monitor.getTags().split(","));
        }
        if (monitor.getMuteEndTime() != null) {
            outVO.setMuteEndTime(monitor.getMuteEndTime().getTime());
        }
        if (monitor.getMuteStartTime() != null) {
            outVO.setMuteStartTime(monitor.getMuteStartTime().getTime());
        }
        outVO.setCreatedTime(monitor.getCreatedTime().getTime());
        outVO.setModifiedTime(monitor.getModifiedTime().getTime());
        outVO.setVersion(monitor.getVersion());
        outVO.setVersionTime(monitor.getVersionTime().getTime());
        return outVO;
    }

    public List<LensMonitorDetailsOutVO> listAvailableMonitorState() {
        List<LensMonitor> lensMonitors = lensMonitorDao.listAvailableMonitorState();
        return getLensMonitorDetailsOutVO(lensMonitors);
    }

    public List<LensMonitorDetailsOutVO> listUpdatedMonitorState(Long lastModifyTime) {
        List<LensMonitor> lensMonitors = lensMonitorDao.listUpdatedMonitorState(new Date(lastModifyTime));
        return getLensMonitorDetailsOutVO(lensMonitors);
    }

    private List<LensMonitorDetailsOutVO> getLensMonitorDetailsOutVO(List<LensMonitor> lensMonitors) {
        List<LensMonitorDetailsOutVO> outVOS = new ArrayList<>();
        lensMonitors.forEach(lensMonitor -> {
            LensMonitorDetailsOutVO outVO = new LensMonitorDetailsOutVO();
            BeanUtils.copyProperties(lensMonitor, outVO);
            outVO.setModifiedTime(lensMonitor.getModifiedTime().getTime());
            outVOS.add(outVO);
        });
        return outVOS;
    }

    @Transactional
    public Boolean saveMonitor(LensMonitorInVO lensMonitorInVO, User user) {
        checkUserApplication(user, lensMonitorInVO.getApplicationId());
        LensMonitor lensMonitor = new LensMonitor();
        BeanUtils.copyProperties(lensMonitorInVO, lensMonitor);
        lensMonitor.setId(null);
        if (!CollectionUtils.isEmpty(lensMonitorInVO.getTags())) {
            lensMonitor.setTags(String.join(",", lensMonitorInVO.getTags()));
        } else {
            lensMonitor.setTags("");
        }
        lensMonitor.setCreatedTime(new Date());
        lensMonitor.setCreator(user.getUsername());
        lensMonitor.setMender(lensMonitor.getCreator());
        lensMonitor.setModifiedTime(lensMonitor.getCreatedTime());
        lensMonitor.setVersion(1);
        lensMonitor.setVersionTime(lensMonitor.getCreatedTime());
        lensMonitorDao.insertSelective(lensMonitor);
        saveMonitorDefine(lensMonitor.getId(), lensMonitorInVO.getDefine(), false);
        saveNotify(lensMonitor.getId(), lensMonitorInVO.getNotify(), false);
        saveTags(lensMonitor.getId(), lensMonitor.getApplicationId(), lensMonitorInVO.getTags(), false);
        sendEvent(lensMonitor, getMonitorEventContent(lensMonitor, lensMonitorInVO.getDefine(), lensMonitorInVO.getNotify()));
        // 如果是动态告警 则 在算法预热期内静默
        if (lensMonitor.getDetectionMethod() == DetectionTypeEnum.DYNAMIC_STATE.getCode()) {
            silenceMonitor(user, lensMonitor);
        }
        return true;
    }

    private void silenceMonitor(User user, LensMonitor lensMonitor) {
        LensScheduleDowntimeInVO downtimeInVO = new LensScheduleDowntimeInVO();
        downtimeInVO.setApplicationId(lensMonitor.getApplicationId());
        downtimeInVO.setMonitorId(lensMonitor.getId());
        downtimeInVO.setMonitorName(lensMonitor.getName());
        Date date = new Date();
        downtimeInVO.setStartTime(DateUtils.addMinutes(date, 1));
        downtimeInVO.setEndTime(DateUtils.addDays(date, algorithmWarmUpPeriod));
        downtimeInVO.setScheduleType(1);
        lensScheduleDowntimeService.saveDowntime(downtimeInVO, user);
    }

    @Transactional
    public Boolean updateMonitor(LensMonitorUpdateInVO lensMonitorInVO, User user) {
        checkUserApplication(user, lensMonitorInVO.getApplicationId());
        LensMonitor lensMonitor = new LensMonitor();
        BeanUtils.copyProperties(lensMonitorInVO, lensMonitor);
        if (!CollectionUtils.isEmpty(lensMonitorInVO.getTags())) {
            lensMonitor.setTags(String.join(",", lensMonitorInVO.getTags()));
        } else {
            lensMonitor.setTags("");
        }
        lensMonitor.setState(MonitorStateEnum.NO_DATA.getState());
        lensMonitor.setMender(user.getUsername());
        lensMonitor.setModifiedTime(new Date());
        lensMonitor.setVersionTime(lensMonitor.getModifiedTime());
        LensMonitor originalMonitor = lensMonitorDao.selectByPrimaryKey(lensMonitorInVO.getId());
        Asserts.assertTrue(originalMonitor != null, Constants.ErrorCode.DATA_NOT_EXIST);
        lensMonitor.setVersion(originalMonitor.getVersion() + 1);
        Asserts.assertTrue(lensMonitorDao.updateByPrimaryKeySelective(lensMonitor) == 1, Constants.ErrorCode.SYSTEM_EXCEPTION);
        saveMonitorDefine(lensMonitor.getId(), lensMonitorInVO.getDefine(), true);
        saveNotify(lensMonitor.getId(), lensMonitorInVO.getNotify(), true);
        saveTags(lensMonitor.getId(), lensMonitor.getApplicationId(), lensMonitorInVO.getTags(), true);
        sendEvent(lensMonitor, getMonitorEventContent(lensMonitor, lensMonitorInVO.getDefine(), lensMonitorInVO.getNotify()));
        lensScheduleDowntimeService.updateMonitorName(lensMonitor.getId(), lensMonitor.getName());
        lensMonitorSnapshotDao.insert(lensMonitor.getId());
        // 如果是动态告警 则 在算法预热期内静默
        if (lensMonitor.getDetectionMethod() == null) {
            lensMonitor.setDetectionMethod(originalMonitor.getDetectionMethod());
        }
        if (lensMonitor.getDetectionMethod() == DetectionTypeEnum.DYNAMIC_STATE.getCode()) {
            silenceMonitor(user, lensMonitor);
        }
        return true;
    }

    private String getMonitorEventContent(LensMonitor lensMonitor, LensMonitorDefineInVO defined, LensMonitorNotifyInVO notify) {
        StringBuilder str = new StringBuilder();
        str.append("告警定义：\\r\\n")
                .append("     1、名称：").append(lensMonitor.getName()).append("\\r\\n")
                .append("     2、查询：").append(
                LensMQLBuilder.buildQueryDSL(
                        defined.getMetricName(), AggregatorEnum.valueOf(defined.getMetricAggr()).toString(),
                        lensMonitor.getIntegrationId().toString(),
                        MapUtils.merge(new HashMap<>(), defined.getScope(), defined.getMetricFilter()),
                        null, defined.getMetricBy(), new PastTimeRange(Long.valueOf(defined.getDuring())),
                        Long.valueOf(defined.getWindowTime()), null)).append("\\r\\n")
                .append("     3、缺失数据是否通知：").append(defined.getNotifyDataMissing()).append("\\r\\n");
        if (notify.getRenotifyTime().compareTo(0) > 0) {
            str.append("     4、重新通知时间：").append(notify.getRenotifyTime()).append("分钟").append("\\r\\n");
        }
        return str.toString();
    }


    @Transactional
    public Boolean resolveMonitorState(List<Long> ids, User user) {
        List<LensMonitor> monitors = checkUserMonitorId(ids, user);
        lensMonitorDao.updateStateByPrimaryKeys(ids, MonitorStateEnum.OK.getState());
        StringBuilder str = new StringBuilder();
        str.append("告警被").append("用户[").append(user.getUsername()).append("]恢复");
        monitors.forEach(lensMonitor -> {
            lensMonitorStateService.artificialRestorationMonitorState(lensMonitor.getId(), lensMonitor.getVersion());
            if (!lensMonitor.getMute() && lensMonitor.getState() != MonitorStateEnum.OK.getState()) {
                sendEvent(lensMonitor, str.toString());
            }
        });
        return true;
    }

    @Transactional
    public Boolean deleteMonitor(List<Long> ids, User user) {
        List<LensMonitor> lensMonitors = checkUserMonitorId(ids, user);
        lensMonitorDao.deleteByPrimaryKeys(ids);
        StringBuilder str = new StringBuilder();
        str.append("告警被").append("用户[").append(user.getUsername()).append("]删除");
        lensMonitors.forEach(lensMonitor -> {
            lensScheduleDowntimeService.deleteAllDowntimeByMonitorId(lensMonitor.getId(), user.getUsername());
            sendEvent(lensMonitor, str.toString());
        });
        return true;
    }

    private void saveMonitorDefine(Long monitorId, LensMonitorDefineInVO define, boolean modify) {
        if (modify) {
            lensMonitorDefineSnapshotDao.insert(monitorId);
            lensMonitorDefineDao.deleteByMonitorId(monitorId);
        }
        LensMonitorDefine lensMonitorDefine = new LensMonitorDefine();
        BeanUtils.copyProperties(define, lensMonitorDefine);
        if (!CollectionUtils.isEmpty(define.getMetricBy())) {
            lensMonitorDefine.setMetricBy(String.join(",", define.getMetricBy()));
        }
        lensMonitorDefine.setMonitorId(monitorId);
        lensMonitorDefineDao.insertSelective(lensMonitorDefine);
    }

    private void saveNotify(Long monitorId, LensMonitorNotifyInVO notify, boolean modify) {
        if (modify) {
            lensMonitorNotifySnapshotDao.insert(monitorId);
            lensMonitorNotifyDao.deleteByMonitorId(monitorId);
        }
        LensMonitorNotify lensMonitorNotify = new LensMonitorNotify();
        BeanUtils.copyProperties(notify, lensMonitorNotify);
        lensMonitorNotify.setMonitorId(monitorId);
        lensMonitorNotifyDao.insertSelective(lensMonitorNotify);
    }

    private void saveTags(Long monitorId, Long applicationId, List<String> tags, boolean modify) {
        if (modify) {
            lensMonitorTagsDao.deleteByMonitorId(monitorId);
        }
        if (!CollectionUtils.isEmpty(tags)) {
            List<LensMonitorTags> lensMonitorTagsList = new ArrayList<>();
            tags.forEach(s -> {
                LensMonitorTags lensMonitorTags = new LensMonitorTags();
                lensMonitorTags.setMonitorId(monitorId);
                lensMonitorTags.setApplicationId(applicationId);
                lensMonitorTags.setContent(s);
                lensMonitorTagsList.add(lensMonitorTags);
            });
            lensMonitorTagsDao.batchInsert(lensMonitorTagsList);
        }
    }

    public List<String> getAllTags(Long applicationId) {
        return lensMonitorTagsDao.getAllTags(applicationId);
    }
}
